/*
 * text2hash.cpp
 *
 *  Created on: Oct 28, 2012
 *      Author: m
 */

#include <iostream>
#include <string>
#include <sstream>
#include <fstream>
#include <cstring>
#include <exception>
#include "HashFile.h"

#define _TD_PARAM 5
#define _ATTR_DELIM ';'

int blockSize = 0;
bool verifyBucketId = false;

void parseArguments( int argc, char* argv[] )
{

    for( int i = 3; i<argc; ++i )
    {
        if( strcmp(argv[i],"-b") == 0 )
        {
            blockSize = atoi( argv[i+1] );
            i++;
            continue;
        }

        if ( strcmp(argv[i],"-verify-id") == 0 )
        {
            verifyBucketId = true;
            continue;
        }
    }
}

void invalid_args()
{
    std::cerr << "Argumentos inválidos. " << std::endl
            << "Uso: text2hash inputFile hashfile -b blockSize [-verify-id]"
            << std::endl;
    exit( 1 );
}

typedef struct
{
    size_t id;
    std::string phrase;
    std::string surname;
    std::string name;

    bool parseData( std::string &line )
    {
        std::stringstream ss( line );

        char c;
        if ( !( ss >> id && ss >> c && c == _ATTR_DELIM ) )
            return false;

        if ( !getline( ss, phrase, _ATTR_DELIM ) )
            return false;

        if ( !getline( ss, surname, _ATTR_DELIM ) )
            return false;

        if ( !getline( ss, name, _ATTR_DELIM ) )
            name.clear();

        return true;
    }

} HashRegisterBuffer_t;

typedef struct
{
    size_t id;
    size_t td;

    bool parseID( std::string &line )
    {
        std::stringstream ss;
        std::string StringBuffer;

        if ( line.empty() )
            return false;

        ss.str( line );

        if ( !( ss >> StringBuffer && StringBuffer.compare( "bucket" ) == 0 && ss >> id && id > 0 ) )
            return false;

        return true;
    }

    bool parseTD( std::string &line )
    {
        std::stringstream ss;
        std::string StringBuffer;

        if ( line.empty() )
            return false;

        ss.str( line );

        if ( !( ss >> StringBuffer && StringBuffer.compare( "td" ) == 0 && ss >> td ) )
            return false;

        return true;
    }

} HashBucketBuffer_t;


int main( int argc, char* argv[] )
{
    std::unique_ptr<HashFile> hf;
    std::string hashFileName;
    std::ifstream inFile;
    std::string lineBuffer;
    size_t lineNumber = 0;
    bucket_ptr bucket;

    if ( argc < 5 )
        invalid_args();

    // parseo los argumentos
    std::string textFileName = argv[1];
    hashFileName = argv[2];

    parseArguments( argc, argv );

    if ( blockSize <= 0 )
        invalid_args();

    inFile.open( textFileName.c_str() );
    hf.reset( new HashFile( hashFileName, (size_t)blockSize, true ) );

    while( getline( inFile, lineBuffer ) )
    {
        HashBucketBuffer_t bucketData;
        HashRegisterBuffer_t registerData;

        if ( lineBuffer.empty() )
        {
            std::cerr << "La línea " << lineNumber << " está vacía." << std::endl;
            exit(10);
        }

        // Intentar interpretarlo como un bucket
        if ( bucketData.parseID( lineBuffer ) )
        {
            getline( inFile, lineBuffer );
            lineNumber++;
            if ( bucketData.parseTD( lineBuffer ) )
            {
                if ( bucket )
                {
                    hf->saveBucket( bucket );
                }

                // Hay que tener en cuenta que el bucket 1 siempre esta creado,
                //  por lo tanto si estoy intentando insertar en el bucket 1
                //  no creo uno nuevo, sino que pido el 1.

                if ( bucketData.id == 1 )
                {
                    bucket = hf->getBucket( bucketData.id );

                    // Re-seteo el TD porque por default viene con td = 1
                    bucket->setTd( bucketData.td );
                }
                else
                    bucket = hf->getNewBucket( bucketData.td );

                assert( bucket->getTd() == bucketData.td );

                if ( verifyBucketId )
                    assert( bucket->getBucketNr() == bucketData.id );
            }
            else
            {
                std::cerr << "Error al parsear el archivo en la línea: " << lineNumber << std::endl;
                exit( 11 );
            }
        }
        else
        {
            if ( !registerData.parseData( lineBuffer ) )
            {
                std::cerr << "Error al parsear el archivo en la línea: " << lineNumber << std::endl;
                exit( 12 );
            }


            // Si no se pudo interpretar como un bucket, se lo interpreta como un registro
            if ( !( bucket->addRegister( registerData.id, registerData.phrase, registerData.surname, registerData.name ) ) )
            {
                std::cerr << "No se pudo insertar el registro de la línea " << lineNumber << "." << std::endl;
                std::cerr << "El bucket: " << bucket->getBucketNr() << " está lleno." << std::endl;
                exit( 13 );
            }
        }

        lineNumber++;
    }

    hf->saveBucket( bucket );

    return 0;
}
